/*
 Copyright (c) 2009, tomo@android-group-jp
 All rights reserved.

 Redistribution and use in source and binary forms, with or without modification, are permitted provided
 that the following conditions are met:

 ・Redistributions of source code must retain the above copyright notice, this list of conditions and the
   following disclaimer.
 ・Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
   the following disclaimer in the documentation and/or other materials provided with the distribution.
 ・Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or 
   promote products derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
 *  Gainerと通信するためのJNIライブラリ実装部
 *
 *  実装としては単純なシリアル通信だが，Gainer専用として簡単に実装している
 *
 */

#include <fcntl.h>
#include <termios.h>
#include <stdbool.h>
#include <signal.h>

#ifndef _Included_net_android_gainer_Serial
#include "net_android_gainer_Serial.h"
#endif
#ifndef _LIBS_CUTILS_LOG_H
#include "logdebug.h"
#endif

#define TRUE  1
#define FALSE 0

struct termios oldtio, newtio;      /* 通信設定構造体 */
static const int BAUDRATE = B38400; /* ボーレート */
static int fd = 0;                  /* デバイスファイルディスクリプタ */
static bool debug = FALSE;          /* デバッグフラグ */

/* SIGINTを受信するシグナルハンドラ */
void signal_handler_INT(int);

/**
 * Gainerを接続したシリアルポートをオープンする
 * 通信設定も行う
 *
 */
JNIEXPORT jboolean JNICALL Java_net_android_gainer_Serial_openPort(JNIEnv *env, jobject obj, jstring portName, jboolean debug_flg) {
	LOGD_IF(debug, "JNI: +openPort");
	
	/* デバッグフラグの設定 */
	debug = debug_flg;
	/* シグナルを受けた時の動作を定義 */
	struct sigaction saio;
  
	jboolean isCopy = JNI_FALSE;

	const char *port = (*env)->GetStringUTFChars( env, portName, &isCopy );
	LOGD_IF( debug, "JNI: openPort: Device Open...: %s", port );

	/* シリアルのオープン */
	fd = open( port, O_RDWR );
	if ( fd < 0 ) {
		LOGD_IF(debug, "JNI: openPort: open Fail!\n");
		return JNI_FALSE;
	}

	if ( JNI_TRUE == isCopy ) {
		(*env)->ReleaseStringUTFChars( env, portName, port );
	}
	
	/* シグナルハンドラを設定する */
	saio.sa_handler = signal_handler_INT;
	saio.sa_mask = 0;
	saio.sa_flags = 0;
	saio.sa_restorer = NULL;
	sigaction( SIGINT, &saio, NULL );
	/* プロセスが SIGINT を受け取れるようにする */
	fcntl( fd, F_SETOWN, getpid() );
  
	/* 現在の通信設定の退避 */
	ioctl( fd, TCGETS, &oldtio );

	/* 現在の設定をデバッグ表示 */
	LOGD_IF(debug, "Now Setteing");
	LOGD_IF(debug, "Input mode %x", oldtio.c_iflag);
	LOGD_IF(debug, "output mode %x", oldtio.c_oflag);
	LOGD_IF(debug, "Control mode %x", oldtio.c_cflag);
	LOGD_IF(debug, "Local mode %x", oldtio.c_lflag);
	LOGD_IF(debug, "Baud Rate %d", cfgetospeed(&oldtio));

	/* Gainer用の通信設定を行う */
	/* ボーレート 38400　キャラクタビット８　ストップビット１　パリティなし */
	newtio = oldtio;
	newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
	/* 入力は非カノニカル受信とする */
	newtio.c_lflag = 0;
	newtio.c_cc[VTIME] = 0; /* タイマは無し */
	newtio.c_cc[VMIN]  = 2; /* Gainerのコマンドは2文字のため */ 
	/* 新しい設定で上書きする */
	ioctl( fd, TCSETS, &newtio );
	
	/* 設定値のデバッグ表示 */
	LOGD_IF(debug, "New Setteing");
	LOGD_IF(debug, "Input mode %x", newtio.c_iflag);
	LOGD_IF(debug, "output mode %x", newtio.c_oflag);
	LOGD_IF(debug, "Control mode %x", newtio.c_cflag);
	LOGD_IF(debug, "Local mode %x", newtio.c_lflag);
	LOGD_IF(debug, "Baud Rate %d", cfgetospeed(&newtio));
  
	LOGD_IF(debug, "JNI: -openPort");
  
	return TRUE;
}

/**
 * シリアルポートをクローズする
 *
 */
JNIEXPORT void JNICALL Java_net_android_gainer_Serial_closePort(JNIEnv *env, jobject obj) {
	LOGD_IF(debug, "JNI: +closePort");
	
	/* 強制中断のためにSIGINTを送信 */
	raise( SIGINT );
	
	/* 保管しておいた設定を元に戻す */
	ioctl( fd, TCSETS, &oldtio );

	/* シリアルデバイスのクローズ */
	close( fd );
  
	LOGD_IF(debug, "JNI: -closePort");
	
	return;
}

/**
 * Geinerへコマンドを送信する
 *
 */
JNIEXPORT jint JNICALL Java_net_android_gainer_Serial_writePort(JNIEnv *env, jobject obj, jbyteArray data, jint length) {
	LOGD_IF(debug, "JNI: +writePort");
	
	jbyte *buf = NULL;
	jboolean isCopy = JNI_FALSE;

	buf = (*env)->GetByteArrayElements( env, data, &isCopy );

	if ( write( fd, buf, length ) < 0 ) {
		LOGD_IF(debug, "JNI: writePort: Write Error");
		if ( isCopy == JNI_TRUE ) {
			(*env)->ReleaseByteArrayElements( env, data, buf, 0 );
		}
	}

	if ( isCopy == JNI_TRUE ) {
		(*env)->ReleaseByteArrayElements( env, data, buf, 0 );
	}

  	LOGD_IF(debug, "JNI: -writePort");

	return length;
}

/**
 * Gainerからデータを受信する
 *
 */
JNIEXPORT jint JNICALL Java_net_android_gainer_Serial_readPort(JNIEnv *env, jobject obj, jbyteArray data, jint length) {
	LOGD_IF(debug, "JNI: +readPort");

	jboolean isCopy;
	jbyte *buf = NULL;
	jint readSize;

	buf = (*env)->GetByteArrayElements( env, data, &isCopy );
  
	readSize = read( fd, buf, length );
	if ( readSize < 0 ) {
		LOGD_IF(debug, "JNI: readPort: Read Error"); 
	}

	if ( isCopy == JNI_TRUE ) {
		(*env)->ReleaseByteArrayElements( env, data, buf, 0 );
	}

	LOGD_IF(debug, "JNI: -readePort");

	return readSize;
}

/**
 * シグナルハンドラ（中断を受け付けるため）
 *
 */
void signal_handler_INT(int status) {
	LOGD_IF(debug, "JNI: +signal_handler_INT");
	/* 何もしない */
	LOGD_IF(debug, "JNI: -signal_handler_INT");
}
